#include"include.h"

typedef struct {
   char *filename;
   long offset;
   unsigned int datalen;
   void *data;
   void (*callback) (void *, int, void *, int);
   void *cookie;
} filemanageTask_t;

filemanage_t *filemanageInit()
{
   filemanage_t *nf = malloc(sizeof(filemanage_t));
   nf->should_run = 0;
   sem_init(&(nf->runLock), 0, 1);
   sem_init(&(nf->work), 0, 0);
   nf->writeTasks = createList();
   nf->readTasks = createList();

   return nf;
}

void filemanageWriteData(filemanage_t * manager,char *filename,
      long offset, void *data, int datalen)
{  // Construct a write task description
   filemanageTask_t *task = malloc(sizeof(filemanageTask_t));
   task->filename = strdup(filename);
   task->offset = offset;
   task->data = malloc(datalen);
   memcpy(task->data, data, datalen);
   task->datalen = datalen;
   task->callback = NULL;
   task->cookie = NULL;

   // Place the task description in the queue
   listLock(manager->writeTasks);
   addObject(manager->writeTasks, task);
   listRelease(manager->writeTasks);

   // Signal that there is work to be done.
   sem_post(&(manager->work));
}

void filemanageReadData(filemanage_t * manager, char *filename, long offset,
      int datalen, void *cookie, void (*callback) (void *, int, void *, int))
{  // Construct a read task description
   filemanageTask_t *task = malloc(sizeof(filemanageTask_t));
   task->filename = strdup(filename);
   task->offset = offset;
   task->datalen = datalen;
   task->callback = callback;
   task->cookie = cookie;

   // Place the read task description in the queue
   listLock(manager->readTasks);
   addObject(manager->readTasks, task);
   listRelease(manager->readTasks);

   // Signal that there is work to be done.
   sem_post(&(manager->work));
}

void filemanageRun(filemanage_t *manager)
{
   manager->should_run = 1;
   filemanageTask_t *curr;
   FILE *file;
   void *dataBuff;
   int err;

   char *mdir;
   int i;
   sem_wait(&(manager->runLock));
   while(manager->should_run)
   {
      sem_post(&(manager->runLock));
      // Wait for jobs to become availale
      sem_wait(&(manager->work));

      sem_wait(&(manager->runLock));
      if(!(manager->should_run))
      {
         sem_post(&(manager->runLock));
         break;
      }
      sem_post(&(manager->runLock));

      // Execute write tasks
      listLock(manager->writeTasks);
      resetList(manager->writeTasks);
      while(!endList(manager->writeTasks))
      {
         curr = getObject(manager->writeTasks);
         // Make all parent dirs of the file
         // Parse dir string out of the filename
         if(curr->filename == NULL)
            continue; //HACKY FIX, this should be fixed PROPERLY over the summer

         mdir = strdup(curr->filename);
         for(i = strlen(mdir) - 2; i >= 0 && mdir[i] != '/'; i--);
         if(i != -1)
         {
            mdir[i+1] = '\0';
            // Attempt to make nonexistant dirs.
            makeDirectoryTree(mdir);
         }
         free(mdir);

         // Execute the task
         file = fopen(curr->filename, "rb+");
         if(file == NULL)
         {  // If the file doesn't exist, write 1 byte to it.
            file = fopen(curr->filename, "w");
            fwrite("\0", 1, 1, file);
            fclose(file);
            file = fopen(curr->filename, "rb+");
         }
         fseek(file, curr->offset, SEEK_SET);
         fwrite(curr->data, 1, curr->datalen, file);
         fclose(file);
         // Cleanup
         free(curr->filename);
         free(curr->data);
         free(curr);
      }
      clearList(manager->writeTasks);
      listRelease(manager->writeTasks);

      // Execute read tasks
      listLock(manager->readTasks);
      resetList(manager->readTasks);
      while(!endList(manager->readTasks))
      {
         curr = getObject(manager->readTasks);
         // Execute the task
         dataBuff = malloc(curr->datalen);
         file = fopen(curr->filename, "rb");
         fseek(file, curr->offset, SEEK_SET);
         err = (fread(dataBuff, 1, curr->datalen, file) != curr->datalen);
         fclose(file);
         // Call back read function
         curr->callback(dataBuff, curr->datalen, curr->cookie, err);
         // Cleanup
         free(curr->filename);
         free(curr);
      }
      clearList(manager->readTasks);
      listRelease(manager->readTasks);

      sem_wait(&(manager->runLock));
   }
}

void filemanageExitAndCleanup(filemanage_t *manager)
{
   // Signal the thread to stop
   
   sem_wait(&(manager->runLock));
   manager->should_run = 0;
   sem_post(&(manager->runLock));

   sem_post(&(manager->work));
   // Clear lists
   clearList(manager->writeTasks);
   clearList(manager->readTasks);
   free(manager->writeTasks);
   free(manager->readTasks);
   free(manager);
}


void do_mkdir(char *dir)
{
   //TODO this should perhaps check for dir existance first.
   mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
}

void makeDirectoryTree(char *dirs)
{
   char *pp;
   char *sp;
   char *copypath = strdup(dirs);

   pp = copypath;
   while((sp = strchr(pp, '/')) != 0)
   {
      if(sp != pp)
      {
         *sp = '\0';
         do_mkdir(copypath);
         *sp = '/';
      }
      pp = sp + 1;
   }
   free(copypath);
}
